using System;
using System.Collections.Generic;
using JetBrains.ProjectModel;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.ReSharper.UnitTestExplorer;
using JetBrains.Shell;
using JetBrains.Shell.Progress;
using JetBrains.Util.DataStructures;

namespace JamesKovacs.ReSharper.UnitTestSupport.VstsUnit {
    internal class VstsFileExplorer : IRecursiveElementProcessor {
        private readonly string m_AssemblyPath;
        private readonly UnitTestElementLocationConsumer m_Consumer;
        private readonly IFile m_File;
        private readonly Dictionary2<IDeclaredElement, VstsUnitTestFixtureElement> m_Fixtures = new Dictionary2<IDeclaredElement, VstsUnitTestFixtureElement>();
        private readonly CheckForInterrupt m_Interrupted;
        private readonly Dictionary2<IDeclaredElement, int> m_Orders = new Dictionary2<IDeclaredElement, int>();
        private readonly IProject m_Project;
        private readonly IUnitTestProvider m_Provider;

        public VstsFileExplorer(IUnitTestProvider provider, UnitTestElementLocationConsumer consumer, IFile file, CheckForInterrupt interrupted) {
            if(file == null) {
                throw new ArgumentNullException("file");
            }
            if(provider == null) {
                throw new ArgumentNullException("provider");
            }
            m_Consumer = consumer;
            m_Provider = provider;
            m_File = file;
            m_Interrupted = interrupted;
            m_Project = m_File.ProjectItem.GetProject();
            m_AssemblyPath = UnitTestManager.GetOutputAssemblyPath(m_Project).FullPath;
        }

        public bool InteriorShouldBeProcessed(IElement element) {
            if(element is ITypeMemberDeclaration) {
                return (element is ITypeDeclaration);
            }
            return true;
        }

        public void ProcessBeforeInterior(IElement element) {
            IDeclaration declaration = element as IDeclaration;
            if(declaration != null) {
                VstsUnitElementBase testElement = null;
                IDeclaredElement declaredElement = declaration.DeclaredElement;
                ITypeElement typeElement = declaredElement as ITypeElement;
                if((typeElement != null) && VstsUnitTestInspector.IsTestFixture(typeElement)) {
                    VstsUnitTestFixtureElement fixtureElement;
                    if(!m_Fixtures.ContainsKey(typeElement)) {
                        fixtureElement = new VstsUnitTestFixtureElement(m_Provider, m_Project, typeElement.CLRName, m_AssemblyPath);
                        m_Fixtures.Add(typeElement, fixtureElement);
                        m_Orders.Add(typeElement, 0);
                    } else {
                        fixtureElement = m_Fixtures[typeElement];
                    }
                    testElement = fixtureElement;
                    int order = 0;
                    AppendTests(fixtureElement, typeElement.GetSuperTypes(), ref order);
                }
                ITypeMember typeMember = declaredElement as ITypeMember;
                if((typeMember != null) && VstsUnitTestInspector.IsTestMethod(typeMember)) {
                    ITypeElement type = typeMember.GetContainingType();
                    if(type != null) {
                        VstsUnitTestFixtureElement fixtureElement = m_Fixtures[type];
                        if(fixtureElement != null) {
                            int order = m_Orders[type] + 1;
                            m_Orders[type] = order;
                            testElement = new VstsUnitTestElement(m_Provider, fixtureElement, m_Project, type.CLRName, typeMember.ShortName, order);
                        }
                    }
                }
                if(testElement != null) {
                    UnitTestElementDisposition disposition = new UnitTestElementDisposition(testElement, m_File.ProjectItem, declaration.GetNameRange(), declaration.GetDocumentRange().TextRange);
                    m_Consumer(disposition);
                }
            }
        }

        public void ProcessAfterInterior(IElement element) {
        }

        // Properties
        public bool ProcessingIsFinished {
            get {
                if(m_Interrupted()) {
                    throw new ProcessCancelledException();
                }
                return false;
            }
        }

        private void AppendTests(VstsUnitTestFixtureElement fixtureElement, IEnumerable<IDeclaredType> types, ref int order) {
            foreach(IDeclaredType type in types) {
                ITypeElement typeElement = type.GetTypeElement();
                if(typeElement != null) {
                    foreach(ITypeMember member in typeElement.GetMembers()) {
                        if(VstsUnitTestInspector.IsTestMethod(member)) {
                            new VstsUnitTestElement(m_Provider, fixtureElement, m_Project, typeElement.CLRName, member.ShortName, order++);
                        }
                    }
                    AppendTests(fixtureElement, type.GetSuperTypes(), ref order);
                }
            }
        }
    }
}